const Utils = (function() {
  init()

  /**
   * 初始化
   */
  function init() {
    window.$ = $
    window.$all = $all
    HTMLElement.prototype.$fadeIn = fadeIn
    HTMLElement.prototype.$fadeOut = fadeOut
    HTMLElement.prototype.$transitionEnd = transitionEnd
    HTMLElement.prototype.$hide = hide
    HTMLElement.prototype.$show = show
  }

  /**
   * @param {Object} selector
   * @returns {HTMLElement}
   */
  function $(selector) {
    return document.querySelector(selector)
  }

  /**
   * @param {Object} selector
   * @returns {HTMLElement[]}
   */
  function $all(selector) {
    return document.querySelectorAll(selector)
  }

  /**
   * 节流
   * @param {Function} cb 回调函数
   */
  function throttle(cb) {
    let flag = true
    return () => {
      if (!flag) return
      flag = false
      cb.apply(this, arguments)
    }
  }

  /**
   * 淡出
   * @param {Number} duration 淡出时间
   * @returns {Promise<void>}
   */
  function fadeOut(duration = 500) {
    return new Promise((resolve) => {
      animation(duration, 1, 0,
        (val) => {
          this.style.opacity = val
        },
        () => {
          this.style.display = 'none'
          this.style.removeProperty('opacity')
          resolve()
        })
    })
    // return new Promise((resolve) => {
    //   const inter = 4
    //   const x = 1 / (duration / inter)
    //   let i = 1
    //   let timer = setInterval(() => {
    //     i -= x
    //     this.style.opacity = i
    //     if (i <= 0) {
    //       this.style.display = 'none'
    //       this.style.removeProperty('opacity')
    //       clearInterval(timer)
    //       resolve()
    //     }
    //   }, inter)
    // })
  }

  /**
   * 淡入
   * @param {Number} duration 淡入时间
   * @returns {Promise<void>}
   */
  function fadeIn(duration = 500) {
    return new Promise((resolve) => {
      this.style.display = 'block'
      animation(duration, 0, 1,
        (val) => {
          this.style.opacity = val
        },
        () => {
          this.style.removeProperty('opacity')
          resolve()
        })
    })
    // return new Promise((resolve) => {
    //   const inter = 4
    //   const x = 1 / (duration / inter)
    //   let i = 0
    //   this.style.display = 'block'
    //   this.style.opacity = i
    //   let timer = setInterval(() => {
    //     i += x
    //     this.style.opacity = i
    //     if (i >= 1) {
    //       this.style.removeProperty('opacity')
    //       clearInterval(timer)
    //       resolve()
    //     }
    //   }, inter)
    // })
  }

  /**
   * 隐藏
   */
  function hide() {
    this.style.display = 'none'
  }

  /**
   * 显示
   */
  function show() {
    this.style.display = 'block'
  }

  /**
   * 元素过渡结束
   */
  function transitionEnd() {
    return new Promise((resolve) => {
      this.addEventListener(
        'transitionend',
        throttle(() => resolve())
      )
    })
  }

  /**
   * @param {number} duration
   * @param {number} from
   * @param {number} to
   */
  function animation(duration, from, to, onProgress, onFinish) {
    const disp = to - from
    const speed = disp / duration
    const startTime = Date.now()
    let value = from
    onProgress(value)

    function _run() {
      const now = Date.now()
      const time = now - startTime
      if (time >= duration) {
        value = to
        onProgress(value)
        onFinish()
        return
      }
      const d = speed * time
      value = d + from
      onProgress(value)
      requestAnimationFrame(_run)
    }

    requestAnimationFrame(_run)
  }

  return {
    throttle
  }
})()
